package com.jboltai.pptextract;

import org.apache.poi.xslf.usermodel.*;
import org.apache.poi.hslf.usermodel.*;
import org.apache.poi.sl.usermodel.*;
import org.apache.poi.sl.extractor.SlideShowExtractor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.awt.*;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.imageio.ImageIO;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.poi.sl.usermodel.PictureData.PictureType;
import org.apache.poi.xslf.usermodel.XSLFPictureData;
import org.apache.poi.hslf.usermodel.HSLFPictureData;
import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.openxml4j.opc.PackagePart;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;

/**
 * Implementation of PPTProcessor using Apache POI
 */
public class PPTProcessorImpl implements PPTProcessor {
    private static final Logger logger = LoggerFactory.getLogger(PPTProcessorImpl.class);

    @Override
    public void convertToPdf(File pptFile, File outputFile) throws IOException {
        String fileName = pptFile.getName().toLowerCase();

        try (PDDocument pdfDocument = new PDDocument()) {
            if (fileName.endsWith(".pptx")) {
                try (XMLSlideShow ppt = new XMLSlideShow(new FileInputStream(pptFile))) {
                    convertSlidesToPdf(ppt, pdfDocument);
                }
            } else if (fileName.endsWith(".ppt")) {
                try (HSLFSlideShow ppt = new HSLFSlideShow(new FileInputStream(pptFile))) {
                    convertSlidesToPdf(ppt, pdfDocument);
                }
            } else {
                throw new IllegalArgumentException("Unsupported file format: " + fileName);
            }

            pdfDocument.save(outputFile);
            logger.info("PDF saved to: {}", outputFile.getAbsolutePath());
        }
    }

    private void convertSlidesToPdf(XMLSlideShow slideShow, PDDocument pdfDocument) throws IOException {
        Dimension pgsize = slideShow.getPageSize();
        List<XSLFSlide> slides = slideShow.getSlides();

        for (XSLFSlide slide : slides) {
            BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_RGB);
            Graphics2D graphics = img.createGraphics();

            // Clear the drawing area
            graphics.setPaint(Color.WHITE);
            graphics.fill(new Rectangle2D.Float(0, 0, pgsize.width, pgsize.height));

            // Render the slide
            slide.draw(graphics);
            graphics.dispose();

            // Convert to PDF
            PDPage page = new PDPage(new PDRectangle(pgsize.width, pgsize.height));
            pdfDocument.addPage(page);

            PDImageXObject pdImage = PDImageXObject.createFromByteArray(
                    pdfDocument,
                    bufferedImageToByteArray(img),
                    "slide");

            try (PDPageContentStream contentStream = new PDPageContentStream(pdfDocument, page)) {
                contentStream.drawImage(pdImage, 0, 0, pgsize.width, pgsize.height);
            }
        }
    }

    private void convertSlidesToPdf(HSLFSlideShow slideShow, PDDocument pdfDocument) throws IOException {
        Dimension pgsize = slideShow.getPageSize();
        List<HSLFSlide> slides = slideShow.getSlides();

        for (HSLFSlide slide : slides) {
            BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_RGB);
            Graphics2D graphics = img.createGraphics();

            // Clear the drawing area
            graphics.setPaint(Color.WHITE);
            graphics.fill(new Rectangle2D.Float(0, 0, pgsize.width, pgsize.height));

            // Render the slide
            slide.draw(graphics);
            graphics.dispose();

            // Convert to PDF
            PDPage page = new PDPage(new PDRectangle(pgsize.width, pgsize.height));
            pdfDocument.addPage(page);

            PDImageXObject pdImage = PDImageXObject.createFromByteArray(
                    pdfDocument,
                    bufferedImageToByteArray(img),
                    "slide");

            try (PDPageContentStream contentStream = new PDPageContentStream(pdfDocument, page)) {
                contentStream.drawImage(pdImage, 0, 0, pgsize.width, pgsize.height);
            }
        }
    }

    private byte[] bufferedImageToByteArray(BufferedImage image) throws IOException {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(image, "png", baos);
        return baos.toByteArray();
    }

    @Override
    public String extractText(File pptFile) throws IOException {
        String fileName = pptFile.getName().toLowerCase();
        String text;

        if (fileName.endsWith(".pptx")) {
            try (XMLSlideShow ppt = new XMLSlideShow(new FileInputStream(pptFile))) {
                SlideShowExtractor<XSLFShape, XSLFTextParagraph> extractor = new SlideShowExtractor<>(ppt);
                extractor.setMasterByDefault(true);
                extractor.setNotesByDefault(true);
                extractor.setCommentsByDefault(true);
                text = extractor.getText();
            }
        } else if (fileName.endsWith(".ppt")) {
            try (HSLFSlideShow ppt = new HSLFSlideShow(new FileInputStream(pptFile))) {
                SlideShowExtractor<HSLFShape, HSLFTextParagraph> extractor = new SlideShowExtractor<>(ppt);
                extractor.setMasterByDefault(true);
                extractor.setNotesByDefault(true);
                extractor.setCommentsByDefault(true);
                text = extractor.getText();
            }
        } else {
            throw new IllegalArgumentException("Unsupported file format: " + fileName);
        }

        // Format the extracted text
        return formatExtractedText(text);
    }

    private String formatExtractedText(String text) {
        if(text == null || text.trim().isEmpty()){
            return "";
        }
        // Remove excessive blank lines
        text = text.replaceAll("(?m)^[ \t]*\r?\n", "");
        // Remove excessive spaces
        text = text.replaceAll("[ \t]+", " ");
        // Clean up paragraph breaks
        text = text.replaceAll("\n{3,}", "\n\n");

        return text.trim();
    }

    @Override
    public void convertToMarkdown(File pptFile, File outputFile) throws IOException {
        String fileName = pptFile.getName().toLowerCase();
        StringBuilder markdown = new StringBuilder();

        if (fileName.endsWith(".pptx")) {
            try (XMLSlideShow ppt = new XMLSlideShow(new FileInputStream(pptFile))) {
                extractSlidesToMarkdown(ppt.getSlides(), markdown);
            }
        } else if (fileName.endsWith(".ppt")) {
            try (HSLFSlideShow ppt = new HSLFSlideShow(new FileInputStream(pptFile))) {
                extractHSLFSlidesToMarkdown(ppt.getSlides(), markdown);
            }
        } else {
            throw new IllegalArgumentException("Unsupported file format: " + fileName);
        }

        try (FileWriter writer = new FileWriter(outputFile)) {
            writer.write(markdown.toString());
        }

        logger.info("Markdown saved to: {}", outputFile.getAbsolutePath());
    }

    private void extractSlidesToMarkdown(List<XSLFSlide> slides, StringBuilder markdown) {
        int slideNumber = 1;

        for (XSLFSlide slide : slides) {
            // Add slide title
            markdown.append("# Slide ").append(slideNumber++).append("\n\n");

            // Extract title if available
            String title = getSlideTitle(slide);
            if (title != null && !title.isEmpty()) {
                markdown.append("## ").append(title).append("\n\n");
            }

            // Extract text content
            List<String> textContent = new ArrayList<>();
            for (XSLFShape shape : slide.getShapes()) {
                if (shape instanceof XSLFTextShape) {
                    XSLFTextShape textShape = (XSLFTextShape) shape;
                    String text = textShape.getText();
                    if (text != null && !text.trim().isEmpty() && !text.equals(title)) {
                        textContent.add(text.trim());
                    }
                }
            }

            // Add content to markdown
            for (String content : textContent) {
                markdown.append(content).append("\n\n");
            }

            markdown.append("---\n\n");
        }
    }

    private void extractHSLFSlidesToMarkdown(List<HSLFSlide> slides, StringBuilder markdown) {
        int slideNumber = 1;

        for (HSLFSlide slide : slides) {
            // Add slide title
            markdown.append("# Slide ").append(slideNumber++).append("\n\n");

            // Extract title if available
            String title = getHSLFSlideTitle(slide);
            if (title != null && !title.isEmpty()) {
                markdown.append("## ").append(title).append("\n\n");
            }

            // Extract text content
            List<String> textContent = new ArrayList<>();
            for (HSLFShape shape : slide.getShapes()) {
                if (shape instanceof HSLFTextShape) {
                    HSLFTextShape textShape = (HSLFTextShape) shape;
                    String text = textShape.getText();
                    if (text != null && !text.trim().isEmpty() && !text.equals(title)) {
                        textContent.add(text.trim());
                    }
                }
            }

            // Add content to markdown
            for (String content : textContent) {
                markdown.append(content).append("\n\n");
            }

            markdown.append("---\n\n");
        }
    }

    private String getSlideTitle(XSLFSlide slide) {
        for (XSLFShape shape : slide.getShapes()) {
            if (shape instanceof XSLFTextShape) {
                XSLFTextShape textShape = (XSLFTextShape) shape;

                // Check if this is likely a title (usually first text shape)
                if (textShape.getText() != null && !textShape.getText().trim().isEmpty()) {
                    return textShape.getText().trim();
                }
            }
        }

        return null;
    }

    private String getHSLFSlideTitle(HSLFSlide slide) {
        for (HSLFShape shape : slide.getShapes()) {
            if (shape instanceof HSLFTextShape) {
                HSLFTextShape textShape = (HSLFTextShape) shape;

                // Check if this is likely a title (usually first text shape)
                if (textShape.getText() != null && !textShape.getText().trim().isEmpty()) {
                    return textShape.getText().trim();
                }
            }
        }

        return null;
    }

    @Override
    public int extractMedia(File pptFile, File outputDir) throws IOException {
        String fileName = pptFile.getName().toLowerCase();
        int mediaCount = 0;

        // Create output directory if it doesn't exist
        if (!outputDir.exists()) {
            outputDir.mkdirs();
        }

        if (fileName.endsWith(".pptx")) {
            try (XMLSlideShow ppt = new XMLSlideShow(new FileInputStream(pptFile))) {
                mediaCount = extractMediaFromXSLF(ppt, outputDir);
            }
        } else if (fileName.endsWith(".ppt")) {
            try (HSLFSlideShow ppt = new HSLFSlideShow(new FileInputStream(pptFile))) {
                mediaCount = extractMediaFromHSLF(ppt, outputDir);
            }
        } else {
            throw new IllegalArgumentException("Unsupported file format: " + fileName);
        }

        logger.info("Extracted {} media files to: {}", mediaCount, outputDir.getAbsolutePath());
        return mediaCount;
    }

    private int extractMediaFromXSLF(XMLSlideShow ppt, File outputDir) throws IOException {
        int count = 0;

        // Extract pictures
        for (XSLFPictureData pictureData : ppt.getPictureData()) {
            PictureType type = pictureData.getType();
            String extension = type != null ? getExtensionFromPictureType(type) : ".bin";
            File outputFile = new File(outputDir, "image_" + count + extension);

            try (FileOutputStream fos = new FileOutputStream(outputFile)) {
                fos.write(pictureData.getData());
                count++;
            }
        }

        // Extract embedded objects (videos, audio, etc.)
        try {
            for (POIXMLDocumentPart documentPart : ppt.getRelations()) {
                if (documentPart instanceof XSLFObjectData) {
                    XSLFObjectData objData = (XSLFObjectData) documentPart;
                    PackagePart part = objData.getPackagePart();
                    String contentType = part.getContentType();
                    String extension = getExtensionFromContentType(contentType);
                    
                    if (extension != null) {
                        File outputFile = new File(outputDir, "media_" + count + extension);
                        try (InputStream is = part.getInputStream();
                             FileOutputStream fos = new FileOutputStream(outputFile)) {
                            byte[] buffer = new byte[8192];
                            int bytesRead;
                            while ((bytesRead = is.read(buffer)) != -1) {
                                fos.write(buffer, 0, bytesRead);
                            }
                            count++;
                        }
                    }
                }
            }
        } catch (Exception e) {
            logger.error("Error extracting embedded objects: {}", e.getMessage());
        }

        return count;
    }

    private int extractMediaFromHSLF(HSLFSlideShow ppt, File outputDir) throws IOException {
        int count = 0;

        // Extract pictures
        for (HSLFPictureData pictureData : ppt.getPictureData()) {
            PictureType type = pictureData.getType();
            String extension = type != null ? getExtensionFromPictureType(type) : ".bin";
            File outputFile = new File(outputDir, "image_" + count + extension);

            try (FileOutputStream fos = new FileOutputStream(outputFile)) {
                fos.write(pictureData.getData());
                count++;
            }
        }

        // HSLF doesn't have a direct way to extract embedded objects like videos/audio
        // We would need to parse the binary format which is complex

        return count;
    }

    private String getExtensionFromPictureType(PictureType type) {
        if (type == null) {
            return ".bin";
        }
        
        switch (type) {
            case JPEG:
                return ".jpg";
            case PNG:
                return ".png";
            case GIF:
                return ".gif";
            case BMP:
                return ".bmp";
            case WMF:
                return ".wmf";
            case EMF:
                return ".emf";
            case PICT:
                return ".pict";
            case DIB:
                return ".dib";
            case TIFF:
                return ".tiff";
            default:
                return ".bin";
        }
    }

    private String getExtensionFromContentType(String contentType) {
        if (contentType == null) return null;

        contentType = contentType.toLowerCase();

        if (contentType.contains("video/mp4")) return ".mp4";
        if (contentType.contains("video/avi")) return ".avi";
        if (contentType.contains("video/mpeg")) return ".mpg";
        if (contentType.contains("video/quicktime")) return ".mov";
        if (contentType.contains("video/x-ms-wmv")) return ".wmv";

        if (contentType.contains("audio/mp3")) return ".mp3";
        if (contentType.contains("audio/mp4")) return ".m4a";
        if (contentType.contains("audio/mpeg")) return ".mp3";
        if (contentType.contains("audio/wav")) return ".wav";
        if (contentType.contains("audio/x-ms-wma")) return ".wma";

        if (contentType.contains("image/")) return ".img";

        return ".bin";  // Default binary extension
    }

    @Override
    public void extractFullContent(File pptFile, File outputDir) throws IOException {
        String fileName = pptFile.getName().toLowerCase();

        // Create output directory if it doesn't exist
        if (!outputDir.exists()) {
            outputDir.mkdirs();
        }

        // Extract text content
        String text = extractText(pptFile);

        // Create media directory
        File mediaDir = new File(outputDir, "media");
        if (!mediaDir.exists()) {
            mediaDir.mkdirs();
        }

        // Extract media files
        int mediaCount = extractMedia(pptFile, mediaDir);

        // Create a report with text and media references
        StringBuilder report = new StringBuilder();
        report.append("# PPT Content Extraction Report\n\n");
        report.append("## Text Content\n\n");
        report.append(text).append("\n\n");

        report.append("## Media Files\n\n");
        if (mediaCount > 0) {
            File[] mediaFiles = mediaDir.listFiles();
            if (mediaFiles != null) {
                for (File mediaFile : mediaFiles) {
                    String relativePath = "media/" + mediaFile.getName();
                    report.append("- [").append(mediaFile.getName()).append("](").append(relativePath).append(")\n");
                }
            }
        } else {
            report.append("No media files found in the presentation.\n");
        }

        // Write the report
        File reportFile = new File(outputDir, "extraction_report.md");
        try (FileWriter writer = new FileWriter(reportFile)) {
            writer.write(report.toString());
        }

        // Also create an HTML version of the report for easy viewing
        createHtmlReport(outputDir, text, mediaDir);

        logger.info("Full content extraction completed. Report saved to: {}", reportFile.getAbsolutePath());
    }

    private void createHtmlReport(File outputDir, String text, File mediaDir) throws IOException {
        StringBuilder html = new StringBuilder();
        html.append("<!DOCTYPE html>\n")
            .append("<html lang=\"en\">\n")
            .append("<head>\n")
            .append("    <meta charset=\"UTF-8\">\n")
            .append("    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n")
            .append("    <title>ppt Content Extraction Report</title>\n")
            .append("    <style>\n")
            .append("        body { font-family: Arial, sans-serif; line-height: 1.6; margin: 0; padding: 20px; }\n")
            .append("        h1, h2 { color: #333; }\n")
            .append("        .media-gallery { display: flex; flex-wrap: wrap; gap: 10px; }\n")
            .append("        .media-item { margin: 10px; text-align: center; }\n")
            .append("        .media-item img { max-width: 300px; max-height: 300px; border: 1px solid #ddd; }\n")
            .append("        .media-item video, .media-item audio { display: block; margin-bottom: 5px; }\n")
            .append("        pre { white-space: pre-wrap; background: #f5f5f5; padding: 10px; border-radius: 5px; }\n")
            .append("    </style>\n")
            .append("</head>\n")
            .append("<body>\n")
            .append("    <h1>ppt Content Extraction Report</h1>\n")
            .append("    <h2>Text Content</h2>\n")
            .append("    <pre>").append(escapeHtml(text)).append("</pre>\n\n")
            .append("    <h2>Media Files</h2>\n");

        // Add media gallery
        html.append("    <div class=\"media-gallery\">\n");

        File[] mediaFiles = mediaDir.listFiles();
        if (mediaFiles != null && mediaFiles.length > 0) {
            for (File mediaFile : mediaFiles) {
                String fileName = mediaFile.getName();
                String relativePath = "media/" + fileName;
                html.append("        <div class=\"media-item\">\n");

                if (fileName.matches("(?i).*\\.(jpg|jpeg|png|gif|bmp)$")) {
                    // Image file
                    html.append("            <img src=\"").append(relativePath).append("\" alt=\"").append(fileName).append("\">\n");
                } else if (fileName.matches("(?i).*\\.(mp4|avi|mov|wmv)$")) {
                    // Video file
                    html.append("            <video controls width=\"300\">\n")
                        .append("                <source src=\"").append(relativePath).append("\" type=\"video/").append(getVideoMimeType(fileName)).append("\">\n")
                        .append("                Your browser does not support the video tag.\n")
                        .append("            </video>\n");
                } else if (fileName.matches("(?i).*\\.(mp3|wav|ogg|m4a|wma)$")) {
                    // Audio file
                    html.append("            <audio controls>\n")
                        .append("                <source src=\"").append(relativePath).append("\" type=\"audio/").append(getAudioMimeType(fileName)).append("\">\n")
                        .append("                Your browser does not support the audio tag.\n")
                        .append("            </audio>\n");
                } else {
                    // Other file - just show a link
                    html.append("            <a href=\"").append(relativePath).append("\">").append(fileName).append("</a>\n");
                }

                html.append("            <div>").append(fileName).append("</div>\n")
                    .append("        </div>\n");
            }
        } else {
            html.append("        <p>No media files found in the presentation.</p>\n");
        }

        html.append("    </div>\n")
            .append("</body>\n")
            .append("</html>");

        // Write the HTML report
        File htmlReportFile = new File(outputDir, "extraction_report.html");
        try (FileWriter writer = new FileWriter(htmlReportFile)) {
            writer.write(html.toString());
        }
    }

    private String escapeHtml(String text) {
        return text.replace("&", "&amp;")
                  .replace("<", "&lt;")
                  .replace(">", "&gt;")
                  .replace("\"", "&quot;")
                  .replace("'", "&#39;");
    }

    private String getVideoMimeType(String fileName) {
        String extension = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
        switch (extension) {
            case "mp4": return "mp4";
            case "webm": return "webm";
            case "ogg": return "ogg";
            default: return "mp4";  // Default
        }
    }

    private String getAudioMimeType(String fileName) {
        String extension = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
        switch (extension) {
            case "mp3": return "mpeg";
            case "ogg": return "ogg";
            case "wav": return "wav";
            default: return "mpeg";  // Default
        }
    }
}
